#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#include "vm.h"
#include "queue.h"
#include "dbg.h"
#include "paging.h"

#define QUANTUM 40
#define DEBUG 1
#define VERBOSE 1


list rdyQ; // queue for ready processes
list* sndQ; // queue for sending processes
list* rcvQ; // queue for receiving processes

/* process 4 */
list unloadedQ; //queue for processes waiting to be loaded into mem
list freePages; // free page list

/* added in project 3
 * additional loop control variable for custom
 * SIGINT catcher. Allows a deadlockless program
 * to run forever but still report context switches
 * when interrupted.
 */
int signal_control = 1;


int execute(brain10* vm, FILE* fileStream, int quantum);

void signalHandler(int sig){
	signal_control = 0;
}

int main(int argc, char* argv[]) {

	/* initialize the free page list */
	int i;
	for(i = 0; i < FRAMES; i++){
		int* n = (int*)malloc(sizeof(int));
		*n = i;
		//		printf("Setting %d: \n", i);
		append(&freePages, n, 0);
	}



	(void) signal(SIGINT, signalHandler);

	FILE* fileStream;
	brain10 vm;
//	reset(&vm); //initialize register values
	setReadyQ(&rdyQ); /* added in project 3 */
	initSemaphores(&vm, 1);

	//	initPageTable(&vm, 10);
	//	printf("TEST: %i", translateAddress("35", vm.pt) );

	/*if a file was passed from command line*/
	if (argc == 2) {
		fileStream = loadProgram(&vm, argv[1], &rdyQ, &unloadedQ, &freePages);
	} else {
		char file[50];
		printf("Filename of brain program: ");
		scanf("%s", file);
		fileStream = loadProgram(&vm, file, &rdyQ, &unloadedQ, &freePages);
	}

	/* initialize mailboxes */
#ifdef DEBUG
	int len = rdyQ.length+1;
#endif
	sndQ = (list*) malloc((sizeof(list)*(rdyQ.length+1)));
	rcvQ = (list*) malloc((sizeof(list)*(rdyQ.length+1)));

	/* initialize main lists. Mainly for the translate function
	 * in vm.c to be able to cause process halts without needing
	 * to pass these to every function that requires address
	 * translation */
	brain_freePages = &freePages;
	brain_vm = &vm;
	brain_rdyQ = &rdyQ;
	brain_unloadedQ = &unloadedQ;


	/* main execution loop */
	int switches = 0;
	while (signal_control && rdyQ.length > 0) {
#ifdef DEBUG
//		printf("\n\n\n****** context switch *******\n");
#endif
		loadContext(&vm, rdyQ.head->data);
		execute(&vm, fileStream, QUANTUM);
#ifdef DEBUG
//		printf("Ready Que: \n");
//		printList(&rdyQ);
//		printf("Send Que: \n");
//		int i;
//		for(i = 0; i < len; i++){
//			printf("mailbox %i  ",i);
//			printList(&sndQ[i]);
//		}
//		printf("Rcv Que: \n");
//		for(i = 0; i < len; i++){
//			printf("mailbox %i  ",i);
//			printList(&rcvQ[i]);
//		}
#endif

		switches++;
		//		printSemaphore(&vm.semaphores[5]);
	}


	printf("Finished with %i context switches with a %i quantum\n",switches,QUANTUM);
	return 0;
}

int execute(brain10* vm, FILE* fileStream, int quantum) {

	int ic = atoi(vm->IC);
	char instruction[3];
//	int offset = atoi(vm->BR);

	int frame, offset, queueSize;

	queueSize = rdyQ.length; //consider removing

	int i;
	for (i = 0; i < quantum && queueSize == rdyQ.length; i++) {

		/* get the instruction */
		instruction[2] = '\0';
		frame = translateAddress(vm->IC,vm->pt);
		offset = vm->IC[1] - 48;

		strncpy(instruction, vm->memory[frame][offset], 2);

		/* get the args */
		char args[3] = { vm->memory[frame][offset][2], vm->memory[frame][offset][3], '\0' };
		int x1x2 = atoi(args);


#if defined(DEBUG) && defined(VERBOSE)
		/* dump registers */
//	if(vm->PID[0] == '1' && vm->PID[1] == '9'){		
//		printf("Instruction : %s\n", instruction);
//		printf("Operands    : %s\n", args);
//}

#endif

		if (strncmp(instruction, "LR", 2) == 0) {
			LR(vm, args);
		} else if (strncmp(instruction, "LL", 2) == 0) {
			LL(vm, args);
		} else if (strncmp(instruction, "LH", 2) == 0) {
			LH(vm, args);
		} else if (strncmp(instruction, "SR", 2) == 0) {
			SR(vm, args);
		} else if (strncmp(instruction, "SP", 2) == 0) {
			SP(vm);
		} else if (strncmp(instruction, "PS", 2) == 0) {
			PS(vm);
		} else if (strncmp(instruction, "PH", 2) == 0) {
			PH(vm);
		} else if (strncmp(instruction, "PP", 2) == 0) {
			PP(vm);
		} else if (strncmp(instruction, "CE", 2) == 0) {
			CE(vm, args);
		} else if (strncmp(instruction, "CL", 2) == 0) {
			CL(vm, args);
		} else if (strncmp(instruction, "BT", 2) == 0) {
			if (BT(vm, args)) {
				ic = atoi(vm->IC) - 1;
			}
		} else if (strncmp(instruction, "BU", 2) == 0) {
			BU(vm, args);
			ic = atoi(vm->IC) - 1;
		} else if (strncmp(instruction, "GD", 2) == 0) {
			GD(vm, args, fileStream);
		} else if (strncmp(instruction, "PD", 2) == 0) {
			if(PD(vm, args))
				HA(); //halt on seg fault
		} else if (strncmp(instruction, "AD", 2) == 0) {
			AD(vm, args);
		} else if (strncmp(instruction, "SU", 2) == 0) {
			SU(vm, args);
		} else if (strncmp(instruction, "MU", 2) == 0) {
			MU(vm, args);
		} else if (strncmp(instruction, "DI", 2) == 0) {
			DI(vm, args);
		} else if (strncmp(instruction, "AS", 2) == 0) {
			AS(vm);
		} else if (strncmp(instruction, "SS", 2) == 0) {
			SS(vm);
		} else if (strncmp(instruction, "MS", 2) == 0) {
			MS(vm);
		} else if (strncmp(instruction, "DS", 2) == 0) {
			DS(vm);
		} else if (strncmp(instruction, "NP", 2) == 0) {

		} else if (strncmp(instruction, "HA", 2) == 0) {
#ifdef DEBUG
			pcb* tmpPcb = (pcb*) rdyQ.head->data;
			printf("***********************************\n");
			printf("*     Final State: Process %s     *\n",tmpPcb->PID);
			printf("***********************************\n");
			print(vm);
#endif
			HA(vm, &rdyQ, &freePages);
		} else if (strncmp(instruction, "SD", 2) == 0) {

			snprintf(vm->IC, 3, "%.2i", ic+1); //update register
//			storeContext(rdyQ.head->data, vm);
			if(SD(vm,sndQ,rcvQ,&rdyQ,args)) // 1 if the message was not rdy
				break;

		} else if (strncmp(instruction, "RC", 2) == 0) {

			snprintf(vm->IC, 3, "%.2i", ic+1); //update register
//			storeContext(rdyQ.head->data, vm);
			if(RC(vm,sndQ,rcvQ,&rdyQ,args)) // 1 if the message was not rdy
				break;

		} else if (strncmp(instruction, "GP", 2) == 0) {
			GP(vm);
		} else if (strncmp(instruction, "LS", 2) == 0) {
			LS(vm, x1x2);
		} else if (strncmp(instruction, "ST", 2) == 0) {
			ST(vm, x1x2);
		} else if (strncmp(instruction, "PE", 2) == 0) {
			//			snprintf(vm->IC, 3, "%.2i", ic+1); //update register
//			storeContext(rdyQ.head->data, vm);
			if(PE(vm, x1x2)){
				break;
			}
		} else if (strncmp(instruction, "VE", 2) == 0) {
			VE(vm, x1x2);
		} else if (strncmp(instruction, "SI", 2) == 0) {
			SI(vm, x1x2);
		} else {
			printf("Instruction %s not recognized, exiting",instruction);
			exit(1);
		}

		if(halted){
			halted = 0;
			break;
		}

#if defined(DEBUG) && defined(VERBOSE)
//if(vm->PID[0] == '1' && vm->PID[1] == '9'){
//		print(vm);
//}
#endif
		snprintf(vm->IC, 3, "%.2i", ++ic); //update register

		if(i+1 == quantum){
//			storeContext(rdyQ.head->data, vm);
			cycle(&rdyQ);
		}

	}


	return 0;
}
